#include <stdio.h> //printf(), perror()
#include <signal.h> //sigaction()
#include <sys/unistd.h> //fork(), getpid()
//#include <sys/stat.h> //mode constants
#include <sys/mman.h>
#include <fcntl.h> //O_* constants
#include <semaphore.h>
#include <errno.h> //errno
#include <time.h> //time()

#define FAIL(s) \
	{ \
		perror(s); \
		return errno; \
	}

#define INIT_SHARED_SEM(p, init) \
	if (sem_init(p, 1, init) == -1) FAIL("sem_open")

#define SHARED_BUFFER_SIZE 100
typedef struct {
	char buffer[SHARED_BUFFER_SIZE];
	char *buffer_read; //next byte to read
	char *buffer_write; //next byte to write
	sem_t mutex; //to read and/or write data
	sem_t canWrite, canRead;
} SHARED_DATA;


SHARED_DATA *sd;
int stop = 0;


void usr1Handler(int sig) {
	stop = 1;
}

void writer(int id, int delay) {
	char rc;
	srand(time(NULL));
	printf("writer %d has pid=%d\n", id, getpid());
	while (!stop) {
		rc = rand()%26 + 'a';
		sem_wait(&sd->canWrite);
		sem_wait(&sd->mutex);
		*(sd->buffer_write++) = rc;
		if (sd->buffer_write >= sd->buffer+SHARED_BUFFER_SIZE)
			sd->buffer_write = sd->buffer;
		sem_post(&sd->mutex);
		sem_post(&sd->canRead);
		printf("parent wrote %c\n", rc);
		sleep(delay);
	}
	printf("writer %d has been asked to stop\n", id);
}

void reader(int id, int delay) {
	char c;
	printf("reader %d has pid=%d\n", id, getpid());
	while (!stop) {
		sem_wait(&sd->canRead);
		sem_wait(&sd->mutex);
		c = *(sd->buffer_read++);
		if (sd->buffer_read >= sd->buffer+SHARED_BUFFER_SIZE)
			sd->buffer_read = sd->buffer;
		sem_post(&sd->mutex);
		sem_post(&sd->canWrite);
		printf("reader %d has just read %c\n", id, c);
		sleep(delay);
	}
	printf("reader %d has been asked to stop\n", id);
}

int main(int argc, char* argv[]) {
	int pid;
	const struct sigaction sa = {usr1Handler, NULL, 0, 0, NULL};
	//Allocate space for the shared structure
	if ((sd = (SHARED_DATA*)mmap(NULL, sizeof(sd), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0)) == MAP_FAILED)
		FAIL("mmap")
	//Initialize the involved semaphores
	if (sem_init(&sd->mutex, 1, 1)==-1 || sem_init(&sd->canRead, 1, 0)==-1 || sem_init(&sd->canWrite, 1, SHARED_BUFFER_SIZE)==-1)
		FAIL("sem_init")
	//Initialize pointers for RW operations
	sd->buffer_read = sd->buffer_write = sd->buffer;
	//Register a common SIGUSR1 handler
	if (sigaction(SIGUSR1, &sa, NULL) == -1)
		FAIL("sigaction")
	//Spawn child processes
	if ((pid = fork()) == -1)
		FAIL("fork")
	else if (pid)
		writer(0, 1);
	else if ((pid = fork()) == -1)
		FAIL("fork")
	else if (pid)
		reader(0, 2);
	else if ((pid = fork()) == -1)
		FAIL("fork")
	else if (pid)
		writer(1, 4);
	else
		reader(1, 3);
	return 0;
}
